// asmfile.cpp
// Revision 15-jan-2005

#include "asmfile.h"
#include "errorsbuf.h"

#include <vector>
#include <stdexcept>

using std::runtime_error;

#include <assert.h>

#define ASSERT assert

namespace {


class FileNotFound : public runtime_error {
public:
	FileNotFound (const std::string & filename) :
		runtime_error ("File '" + filename + "' not found")
	{ }
};

class InvalidInclude : public runtime_error {
public:
	InvalidInclude (const Token & tok) :
		runtime_error ("Unexpected " + tok.str () +
			" after INCLUDE file name")
	{ }
};

class FileLine {
public:
	FileLine (const std::string & text_n, const Tokenizer & tkz_n,
		size_t linenum_n);
	bool empty () const;
	size_t numline () const;
	Tokenizer & gettkz ();
	const std::string & getstrline () const;
private:
	std::string text;
	Tokenizer tkz;
	size_t linenum;
};

FileLine::FileLine (const std::string & text_n, const Tokenizer & tkz_n,
		size_t linenum_n) :
	text (text_n),
	tkz (tkz_n),
	linenum (linenum_n)
{
}

bool FileLine::empty () const
{
	return tkz.empty ();
}

size_t FileLine::numline () const
{
	return linenum;
}

Tokenizer & FileLine::gettkz ()
{
	return tkz;
}

const std::string & FileLine::getstrline () const
{
	return text;
}


typedef std::vector <FileLine> filelines_t;

class FileRef {
public:
	FileRef (const std::string & name, size_t linebeg);
	void setend (size_t n);

	size_t linebegin () const;
	size_t lineend () const;
	std::string name () const;

	bool lineempty (size_t n) const;
	size_t numline (size_t n) const;
	Tokenizer & gettkz (size_t n);
	const std::string & getstrline (size_t n) const;

	void pushline (const std::string & text, const Tokenizer & tkz,
		size_t realnumline);

    //replace last line with new content
    void replaceLastLine(const std::string & text, const Tokenizer & tkz,
        size_t realnumline);
private:
	std::string filename;
	filelines_t line;
	size_t l_begin;
	size_t l_end;
};

FileRef::FileRef (const std::string & name, size_t linebeg) :
	filename (name),
	l_begin (linebeg)
{ }

void FileRef::setend (size_t n)
{
	l_end= n;
}

size_t FileRef::linebegin () const
{
	return l_begin;
}

size_t FileRef::lineend () const
{
	return l_end;
}

std::string FileRef::name () const
{
	return filename;
}

bool FileRef::lineempty (size_t n) const
{
    if (n >= line.size())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }

	return line [n].empty ();
}

size_t FileRef::numline (size_t n) const
{
    if (n >= line.size())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }

	return line [n].numline ();
}

Tokenizer & FileRef::gettkz (size_t n)
{
    if (n >= line.size())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }

	return line [n].gettkz ();
}

const std::string & FileRef::getstrline (size_t n) const
{
    if (n >= line.size())
    {
        //CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }

	return line [n].getstrline ();
}

void FileRef::pushline (const std::string & text, const Tokenizer & tkz,
	size_t realnumline)
{
	//line.push_back (FileLine (text, tkz) );
	FileLine fl (text, tkz, realnumline);
	line.push_back (fl);
}

void FileRef::replaceLastLine(const std::string & text, const Tokenizer & newtkz,
    size_t realnumline)
{
    //erase last element
    line.pop_back();

    pushline(text, newtkz, realnumline);
}


struct LineContent {
public:
	LineContent (size_t linenumn, size_t filenumn);
	size_t getfileline () const;
	size_t getfilenum () const;
private:
	size_t filenum;
	size_t linenum;
};

LineContent::LineContent (size_t filenumn, size_t linenumn) :
	filenum (filenumn),
	linenum (linenumn)
{ }

size_t LineContent::getfileline () const
{
	return linenum;
}

size_t LineContent::getfilenum () const
{
	return filenum;
}


} // namespace


class AsmFile::In {
public:
	In ();
	void addref ();
	void delref ();

	size_t numlines () const;
	size_t numfiles () const;

	bool lineempty (size_t n) const;
	Tokenizer & gettkz (size_t n);
	const std::string & getstrline (size_t n) const;

	void addincludedir (const std::string & dirname);
	void openis (std::ifstream & is, const std::string & filename,
		std::ios::openmode mode) const;
	void copyfile (FileRef & fr, std::ostream & outverb);
	void loadfile (const std::string & filename, bool nocase,
		std::ostream & outverb, std::ostream& outerr);
	void showlineinfo (std::ostream & os, size_t nline) const;
private:
	In (const In &); // Forbidden.
	In & operator = (const In &); // Forbidden.

	const FileRef & getfile (size_t n) const;
	FileRef & getfile (size_t n);

	const LineContent & getline (size_t n) const;
	LineContent & getline (size_t n);

	size_t numrefs;

	typedef std::vector <LineContent> vlinecont_t;
	vlinecont_t vlinecont;

	std::vector <FileRef> vfileref;

	void pushline (size_t linenum, size_t file);
    void RemoveLastLine();

	// ******** Paths for include ************

	std::vector <std::string> includepath;
};

AsmFile::In::In ()
{
	numrefs= 1;
}

void AsmFile::In::addref ()
{
	++numrefs;
}

void AsmFile::In::delref ()
{
	--numrefs;
	if (numrefs == 0)
		delete this;
}

size_t AsmFile::In::numlines () const
{
	return vlinecont.size ();
}

size_t AsmFile::In::numfiles () const
{
	return vfileref.size ();
}

const FileRef & AsmFile::In::getfile (size_t n) const
{
    if (n >= numfiles())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }
	return vfileref [n];
}

FileRef & AsmFile::In::getfile (size_t n)
{
    if (n >= numfiles())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }
	return vfileref [n];
}

const LineContent & AsmFile::In::getline (size_t n) const
{
    if (n >= numlines())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }
	return vlinecont [n];
}

LineContent & AsmFile::In::getline (size_t n)
{
    if (n >= numlines())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }
	return vlinecont [n];
}

bool AsmFile::In::lineempty (size_t n) const
{
    if (n >= numlines())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }

	//return vlinecont [n].empty ();
	const LineContent & lc= getline (n);
	return getfile (lc.getfilenum () ).lineempty (lc.getfileline () );
}

Tokenizer & AsmFile::In::gettkz (size_t n)
{
    if (n >= numlines())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }

	//return vlinecont [n].gettkz ();
	LineContent & lc= getline (n);
	return getfile (lc.getfilenum () ).gettkz (lc.getfileline () );
}

const std::string & AsmFile::In::getstrline (size_t n) const
{
    if (n >= numlines())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }

	//return vlinecont [n].getstrline ();
	const LineContent & lc= getline (n);
	return getfile (lc.getfilenum () ).getstrline (lc.getfileline () );
}

void AsmFile::In::addincludedir (const std::string & dirname)
{
	std::string aux (dirname);
	std::string::size_type l= aux.size ();
	if (l == 0)
		return;
	char c= aux [l - 1];
	if (c != '\\' && c != '/')
		aux+= '/';
	includepath.push_back (aux);
}

void AsmFile::In::openis (std::ifstream & is, const std::string & filename,
	std::ios::openmode mode) const
{
    if (is.is_open())
    {
        std::string message = "File " + filename + " already opened!";
        CompilationErrors::Add(message, __LINE__, __FILE__, __FUNCTION__);
    }
	is.open (filename.c_str (), mode);
	if (is.is_open () )
		return;
	for (size_t i= 0; i < includepath.size (); ++i)
	{
		std::string path (includepath [i] );
		path+= filename;
		is.clear ();
		is.open (path.c_str (), mode);
		if (is.is_open () )
			return;
	}
	throw FileNotFound (filename);
}

void AsmFile::In::pushline (size_t filenum, size_t linenum)
{
    if (filenum >= vfileref.size())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }

	vlinecont.push_back (LineContent (filenum, linenum) );
}

void AsmFile::In::RemoveLastLine()
{
    vlinecont.pop_back();
}

void AsmFile::In::copyfile (FileRef & fr, std::ostream & outverb)
{
	using std::endl;

	outverb << "Reloading file: " << fr.name () <<
		" in " << numlines () << endl;

	const size_t linebegin= fr.linebegin ();
	const size_t lineend= fr.lineend ();

	for (size_t i= linebegin; i < lineend; ++i)
	{
		LineContent l= getline (i);
		vlinecont.push_back (l);
	}

	outverb << "Finished reloading file: " << fr.name () <<
		" in " << numlines () << endl;
}

void AsmFile::In::loadfile (const std::string & filename, bool nocase,
	std::ostream & outverb, std::ostream& outerr)
{
	using std::endl;

	for (size_t i= 0; i < vfileref.size (); ++i)
	{
		if (vfileref [i].name () == filename)
		{
			copyfile (vfileref [i], outverb);
			return;
		}
	}

	// Load the file in memory.

	outverb << "Loading file: " << filename <<
		" in " << numlines () << endl;

	std::ifstream file;
	openis (file, filename, std::ios::in);

	vfileref.push_back (FileRef (filename, numlines () ) );
	const size_t filenum= vfileref.size () - 1;

	std::string line;
	size_t linenum;
	size_t realnum;

	try
	{
        Tokenizer prevTokenList;
        Token prevToken;
        std::string strPrevLine;

		for (linenum= 0, realnum= 0;
			std::getline (file, line);)
		{
            char * strTemp = new char[(int)line.length() + 1];
            if (line.find(';') != std::string::npos)
            {
                //parse comments
                std::size_t pos = line.find(';');
                if (pos == 0)
                {
                    strTemp[0] = ' '; strTemp[1] = '\0';
                }
                else
                {
                    std::string strUntilComment = line.substr(0, pos);
                    strncpy(strTemp, strUntilComment.c_str(), strlen(strUntilComment.c_str())); strTemp[(int)strUntilComment.length()] = '\0';
                }
            }
            else
            {
                strncpy(strTemp, line.c_str(), strlen(line.c_str()));
                strTemp[(int)line.length()] = '\0';
            }
            char* pch = strtok(strTemp, ":");
            while (pch != NULL)
            {
                std::string strActLine = pch; strActLine += "\n";
                Tokenizer* tzCurr;

                Tokenizer tzTemp(strActLine, nocase);
                Token tok = tzTemp.gettoken();

                //allow to have EQU line under line with LABEL
                if (linenum > 0 && prevToken.type() == TypeIdentifier && tok.type() != TypeIdentifier && prevTokenList.GetTokenList().size() == 1)
                {
                    //LABEL is previously defined so put it into current line to prevent error in EQU definition for instance
                    strActLine = strPrevLine.substr(0, strPrevLine.length() - 1) + " " + strActLine;
                    tzCurr = new Tokenizer(strActLine, nocase);
                    tok = (*tzCurr).gettoken();
                    getfile(filenum).replaceLastLine(strActLine, (*tzCurr), realnum); //remove last line from file lines
                    RemoveLastLine(); //remove last line from map of lines to be compiled

                    strPrevLine = strActLine;
                    linenum--;
                }
                else
                {
                    tzCurr = &tzTemp;
                    strPrevLine = strActLine;
                    getfile(filenum).pushline(strActLine, (*tzCurr), realnum);
                }
                prevTokenList = (*tzCurr);
                prevToken = tok;

                //push line to map(tokenized)
                pushline(filenum, linenum);

                if (tok.type() == TypeINCLUDE)
                {
                    std::string includefile = (*tzCurr).getincludefile();
                    tok = (*tzCurr).gettoken();
                    if (tok.type() != TypeEndLine)
                        throw InvalidInclude(tok);

                    loadfile(includefile, nocase,
                        outverb, outerr);

                    Tokenizer tzaux(TypeEndOfInclude);
                    getfile(filenum).pushline("", tzaux, 0);
                    ++linenum;
                    pushline(filenum, linenum);
                }
                pch = strtok(NULL, ":");
                ++linenum;
            }
			++realnum;
		}
        getfile(filenum).setend(linenum /*numlines()*/);
	}
	catch (...)
	{
		/*outerr << "ERROR on line " << linenum + 1 <<
			" of file " << filename << endl;*/
        char message[512];
        sprintf(message, "%s", line.c_str());
        CompilationErrors::Add(linenum + 1, filename, message);
		throw;
	}

	outverb << "Finished loading file: " << filename <<
		" in " << numlines () << endl;
}

void AsmFile::In::showlineinfo (std::ostream & os, size_t nline) const
{
    if (nline >= numlines())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }

	const LineContent & linf= getline (nline);
	const FileRef & fileref= getfile (linf.getfilenum () );

	int line = fileref.numline (linf.getfileline () ) + 1;
	std::string fileName = fileref.name ();

    try
    {
        CompilationErrors::Add(line, fileName, fileref.getstrline(nline).c_str());
    }
    catch (...)
    {
        CompilationErrors::Add(line, fileName, "");
    }

	/*os << " on line " << fileref.numline (linf.getfileline () ) + 1 <<
		" of file " << fileref.name ();*/
}

//*******************************************************************


AsmFile::AsmFile () :
	pin (new In)
{
}

AsmFile::AsmFile (const AsmFile & af) :
	pin (af.pin)
{
	pin->addref ();
}

AsmFile::~AsmFile ()
{
	pin->delref ();
}

// These functions are for propagate constness to the internal class.

inline AsmFile::In & AsmFile::in ()
{
	return * pin;
}

inline const AsmFile::In & AsmFile::in () const
{
	return * pin;
}

void AsmFile::addincludedir (const std::string & dirname)
{
	in ().addincludedir (dirname);
}

void AsmFile::openis (std::ifstream & is, const std::string & filename,
	std::ios::openmode mode) const
{
	in ().openis (is, filename, mode);
}

void AsmFile::loadfile (const std::string & filename, bool nocase,
	std::ostream & outverb, std::ostream& outerr)
{
	in ().loadfile (filename, nocase, outverb, outerr);
}

bool AsmFile::getvalidline ()
{
	for (;;)
	{
		if (currentline >= in ().numlines () )
			return false;
		//if (! in ().getlinecont (currentline).empty () )
		if (! in ().lineempty (currentline) )
			return true;
		++currentline;
	}
}

bool AsmFile::passeof () const
{
	return currentline >= in ().numlines ();
}

size_t AsmFile::getline () const
{
	return currentline;
}

Tokenizer & AsmFile::getcurrentline ()
{
    if (passeof())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }
	//Tokenizer & tz= in ().getlinecont (currentline).gettkz ();
	Tokenizer & tz= in ().gettkz (currentline);
	tz.reset ();
	return tz;
}

const std::string & AsmFile::getcurrenttext () const
{
    if (passeof())
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }
	//return in ().getlinecont (currentline).getstrline ();
	return in ().getstrline (currentline);
}

void AsmFile::setline (size_t line)
{
	currentline= line;
}

void AsmFile::setendline ()
{
	currentline= in ().numlines ();
}

void AsmFile::beginline ()
{
	currentline= LINE_BEGIN;
}

bool AsmFile::nextline ()
{
	if (currentline == LINE_BEGIN)
		currentline= 0;
	else
	{
		if (passeof () )
			return false;
		++currentline;
	}
	if (! getvalidline () )
		return false;
	return true;
}

void AsmFile::prevline ()
{
    if (currentline <= 0)
    {
        CompilationErrors::Add(__LINE__, __FILE__, __FUNCTION__);
        throw;
    }
	--currentline;
}

void AsmFile::showlineinfo (std::ostream & os, size_t nline) const
{
	in ().showlineinfo (os, nline);
}

void AsmFile::showcurrentlineinfo (std::ostream & os) const
{
    if (passeof () )
		os << " detected after end of file";
	else
		in ().showlineinfo (os, getline () );
}

// End of asmfile.cpp
